將程式的組成轉換成有上下階級的結構(或稱:樹狀結構),方便使用者不論從哪個節點、葉子使用,都可以有相似的執行。
假入要撰寫一個程式,本身是參考現實世界中有上下階級的組織時,而且每個組織的上中下游部門,都可以「提供相同的服務」時,就可以採用 Composite 模式,或者稱作樹狀結構(資料結構的樹狀圖)。
現實中常見的範例有:
Composite 模式強調每個節點都可以提供相同的服務,因此作法會是:
節點、葉子親代:MilitaryUnit
public abstract class MilitaryUnit {
protected String name;
protected int unitCounts;
protected int weaponCounts;
protected MilitaryUnit(String name, int unitCounts, int weaponCounts) {
this.name = name;
this.unitCounts = unitCounts;
this.weaponCounts = weaponCounts;
}
public abstract void add(MilitaryUnit unit);
public abstract void remove(MilitaryUnit unit);
public abstract void display(int depth);
public abstract int reportUnitCounts();
public abstract int reportWeaponCounts();
}
節點子代:ConcreteMilitaryUnit
public class ConcreteMilitaryUnit extends MilitaryUnit {
private ArrayList<MilitaryUnit> militaryUnits = new ArrayList<MilitaryUnit>();
public ConcreteMilitaryUnit(String name) {
super(name, 0, 0);
}
@Override
public void add(MilitaryUnit unit) {
militaryUnits.add(unit);
}
@Override
public void remove(MilitaryUnit unit) {
militaryUnits.remove(unit);
}
@Override
public void display(int depth) {
char[] title = new char[depth];
Arrays.fill(title, '-');
System.out.println(new String(title) + name);
for (MilitaryUnit unit : militaryUnits) {
unit.display(depth + 2);
}
}
@Override
public int reportUnitCounts() {
for (MilitaryUnit militaryUnit : militaryUnits) {
unitCounts += militaryUnit.reportUnitCounts();
}
return unitCounts;
}
@Override
public int reportWeaponCounts() {
for (MilitaryUnit militaryUnit : militaryUnits) {
weaponCounts += militaryUnit.reportWeaponCounts();
}
return weaponCounts;
}
}
葉子子代:NormalSoldier
、LazySoldier
、DrunkSoldier
、DeserterSoldier
public class NormalSoldier extends MilitaryUnit {
public NormalSoldier(String name) {
super(name, 1, 1);
}
@Override
public void add(MilitaryUnit unit) {
}
@Override
public void remove(MilitaryUnit unit) {
}
@Override
public void display(int depth) {
char[] title = new char[depth];
Arrays.fill(title, '-');
System.out.println(new String(title) + name);
}
@Override
public int reportUnitCounts() {
return unitCounts;
}
@Override
public int reportWeaponCounts() {
return weaponCounts;
}
}
public class LazySoldier extends MilitaryUnit {
public LazySoldier(String name) {
super(name, 1, 0);
}
@Override
public void add(MilitaryUnit unit) {
}
@Override
public void remove(MilitaryUnit unit) {
}
@Override
public void display(int depth) {
char[] title = new char[depth];
Arrays.fill(title, '-');
System.out.println(new String(title) + name);
}
@Override
public int reportUnitCounts() {
return unitCounts;
}
@Override
public int reportWeaponCounts() {
return weaponCounts;
}
}
public class DrunkSoldier extends MilitaryUnit {
public DrunkSoldier(String name) {
super(name, 1, 3);
}
@Override
public void add(MilitaryUnit unit) {
}
@Override
public void remove(MilitaryUnit unit) {
}
@Override
public void display(int depth) {
char[] title = new char[depth];
Arrays.fill(title, '-');
System.out.println(new String(title) + name);
}
@Override
public int reportUnitCounts() {
return unitCounts;
}
@Override
public int reportWeaponCounts() {
return weaponCounts;
}
}
public class DeserterSoldier extends MilitaryUnit {
public DeserterSoldier(String name) {
super(name, 0, 1);
}
@Override
public void add(MilitaryUnit unit) {
}
@Override
public void remove(MilitaryUnit unit) {
}
@Override
public void display(int depth) {
char[] title = new char[depth];
Arrays.fill(title, '-');
System.out.println(new String(title) + name);
}
@Override
public int reportUnitCounts() {
return unitCounts;
}
@Override
public int reportWeaponCounts() {
return weaponCounts;
}
}
測試:ArmyCompositeSample
public class ArmyCompositeSample {
public static void main(String[] args) {
MilitaryUnit army = new ConcreteMilitaryUnit("陸軍");
army.add(new NormalSoldier("司令"));
army.add(new NormalSoldier("陸軍通訊兵"));
MilitaryUnit division = new ConcreteMilitaryUnit("第一師");
division.add(new NormalSoldier("師長"));
division.add(new NormalSoldier("第一師通訊兵"));
army.add(division);
MilitaryUnit brigade = new ConcreteMilitaryUnit("第一旅");
brigade.add(new NormalSoldier("旅長"));
brigade.add(new NormalSoldier("第一旅通訊兵"));
division.add(brigade);
MilitaryUnit regiment = new ConcreteMilitaryUnit("第一團");
regiment.add(new NormalSoldier("團長"));
regiment.add(new NormalSoldier("第一團通訊兵"));
brigade.add(regiment);
MilitaryUnit battalion = new ConcreteMilitaryUnit("第一營");
battalion.add(new NormalSoldier("營長"));
battalion.add(new NormalSoldier("第一營通訊兵"));
regiment.add(battalion);
MilitaryUnit company = new ConcreteMilitaryUnit("第一連");
company.add(new NormalSoldier("連長"));
company.add(new NormalSoldier("第一連通訊兵"));
battalion.add(company);
MilitaryUnit platoon = new ConcreteMilitaryUnit("第一排");
platoon.add(new NormalSoldier("排長"));
platoon.add(new NormalSoldier("第一排通訊兵"));
company.add(platoon);
MilitaryUnit squad1 = new ConcreteMilitaryUnit("第一班");
squad1.add(new NormalSoldier("班長"));
squad1.add(new NormalSoldier("第一班通訊兵"));
platoon.add(squad1);
MilitaryUnit fireTeam11 = new ConcreteMilitaryUnit("第一伍");
fireTeam11.add(new NormalSoldier("伍長"));
fireTeam11.add(new NormalSoldier("第一伍通訊兵"));
fireTeam11.add(new NormalSoldier("Roger"));
fireTeam11.add(new NormalSoldier("Jason"));
squad1.add(fireTeam11);
MilitaryUnit fireTeam12 = new ConcreteMilitaryUnit("第二伍");
fireTeam12.add(new NormalSoldier("伍長"));
fireTeam12.add(new LazySoldier("第二伍通訊兵"));
fireTeam12.add(new DrunkSoldier("Rick"));
fireTeam12.add(new DeserterSoldier("Jay"));
squad1.add(fireTeam12);
MilitaryUnit squad2 = new ConcreteMilitaryUnit("第二班");
squad2.add(new NormalSoldier("班長"));
squad2.add(new NormalSoldier("第二班通訊兵"));
platoon.add(squad2);
MilitaryUnit fireTeam21 = new ConcreteMilitaryUnit("第三伍");
fireTeam21.add(new NormalSoldier("伍長"));
fireTeam21.add(new DrunkSoldier("第三伍通訊兵"));
fireTeam21.add(new NormalSoldier("Allen"));
fireTeam21.add(new NormalSoldier("Bill"));
squad2.add(fireTeam21);
MilitaryUnit fireTeam22 = new ConcreteMilitaryUnit("第四伍");
fireTeam22.add(new NormalSoldier("伍長"));
fireTeam22.add(new LazySoldier("第四伍通訊兵"));
fireTeam22.add(new DrunkSoldier("Charlie"));
fireTeam22.add(new DeserterSoldier("Dave"));
squad2.add(fireTeam22);
System.out.println("結構圖:");
army.display(1);
System.out.println("陸軍人員回報:" + army.reportUnitCounts());
System.out.println("陸軍武器數量回報:" + army.reportWeaponCounts());
}
}
節點、葉子親代:MilitaryUnit
/** @abstract */
class MilitaryUnit {
constructor(name, unitCounts, weaponCounts) {
this.name = name;
this.unitCounts = unitCounts | 0;
this.weaponCounts = weaponCounts | 0;
}
/** @abstract */
add(unit) { return; }
/** @abstract */
remove(unit) { return; }
/** @abstract */
display(depth) { return; }
/** @abstract */
reportUnitCounts() { return; }
/** @abstract */
reportWeaponCounts() { return; }
}
節點子代:ConcreteMilitaryUnit
class ConcreteMilitaryUnit extends MilitaryUnit {
constructor(name) {
super(name);
/** @type MilitaryUnit[] */
this.militaryUnits = [];
}
/** @override */
add(unit) {
this.militaryUnits.push(unit);
}
/** @override */
remove(unit) {
this.militaryUnits = this.militaryUnits.filter((curUnit) => curUnit !== unit);
}
/** @override */
display(depth) {
console.log('-'.repeat(depth) + this.name);
for (const unit of this.militaryUnits) {
unit.display(depth + 2);
}
}
/** @override */
reportUnitCounts() {
for (const militaryUnit of this.militaryUnits) {
this.unitCounts += militaryUnit.reportUnitCounts();
}
return this.unitCounts;
}
/** @override */
reportWeaponCounts() {
for (const militaryUnit of this.militaryUnits) {
this.weaponCounts += militaryUnit.reportWeaponCounts();
}
return this.weaponCounts;
}
}
葉子子代:NormalSoldier
、LazySoldier
、DrunkSoldier
、DeserterSoldier
class NormalSoldier extends MilitaryUnit {
constructor(name) {
super(name, 1, 1);
}
/** @override */
add(unit) {
return;
}
/** @override */
remove(unit) {
return;
}
/** @override */
display(depth) {
console.log('-'.repeat(depth) + this.name);
}
/** @override */
reportUnitCounts() {
return this.unitCounts;
}
/** @override */
reportWeaponCounts() {
return this.weaponCounts;
}
}
class LazySoldier extends MilitaryUnit {
constructor(name) {
super(name, 1, 0);
}
/** @override */
add(unit) {
return;
}
/** @override */
remove(unit) {
return;
}
/** @override */
display(depth) {
console.log('-'.repeat(depth) + this.name);
}
/** @override */
reportUnitCounts() {
return this.unitCounts;
}
/** @override */
reportWeaponCounts() {
return this.weaponCounts;
}
}
class DrunkSoldier extends MilitaryUnit {
constructor(name) {
super(name, 1, 3);
}
/** @override */
add(unit) {
return;
}
/** @override */
remove(unit) {
return;
}
/** @override */
display(depth) {
console.log('-'.repeat(depth) + this.name);
}
/** @override */
reportUnitCounts() {
return this.unitCounts;
}
/** @override */
reportWeaponCounts() {
return this.weaponCounts;
}
}
class DeserterSoldier extends MilitaryUnit {
constructor(name) {
super(name, 0, 1);
}
/** @override */
add(unit) {
return;
}
/** @override */
remove(unit) {
return;
}
/** @override */
display(depth) {
console.log('-'.repeat(depth) + this.name);
}
/** @override */
reportUnitCounts() {
return this.unitCounts;
}
/** @override */
reportWeaponCounts() {
return this.weaponCounts;
}
}
測試:armyCompositeSample
const armyCompositeSample = () => {
const army = new ConcreteMilitaryUnit("陸軍");
army.add(new NormalSoldier("司令"));
army.add(new NormalSoldier("陸軍通訊兵"));
const division = new ConcreteMilitaryUnit("第一師");
division.add(new NormalSoldier("師長"));
division.add(new NormalSoldier("第一師通訊兵"));
army.add(division);
const brigade = new ConcreteMilitaryUnit("第一旅");
brigade.add(new NormalSoldier("旅長"));
brigade.add(new NormalSoldier("第一旅通訊兵"));
division.add(brigade);
const regiment = new ConcreteMilitaryUnit("第一團");
regiment.add(new NormalSoldier("團長"));
regiment.add(new NormalSoldier("第一團通訊兵"));
brigade.add(regiment);
const battalion = new ConcreteMilitaryUnit("第一營");
battalion.add(new NormalSoldier("營長"));
battalion.add(new NormalSoldier("第一營通訊兵"));
regiment.add(battalion);
const company = new ConcreteMilitaryUnit("第一連");
company.add(new NormalSoldier("連長"));
company.add(new NormalSoldier("第一連通訊兵"));
battalion.add(company);
const platoon = new ConcreteMilitaryUnit("第一排");
platoon.add(new NormalSoldier("排長"));
platoon.add(new NormalSoldier("第一排通訊兵"));
company.add(platoon);
const squad1 = new ConcreteMilitaryUnit("第一班");
squad1.add(new NormalSoldier("班長"));
squad1.add(new NormalSoldier("第一班通訊兵"));
platoon.add(squad1);
const fireTeam11 = new ConcreteMilitaryUnit("第一伍");
fireTeam11.add(new NormalSoldier("伍長"));
fireTeam11.add(new NormalSoldier("第一伍通訊兵"));
fireTeam11.add(new NormalSoldier("Roger"));
fireTeam11.add(new NormalSoldier("Jason"));
squad1.add(fireTeam11);
const fireTeam12 = new ConcreteMilitaryUnit("第二伍");
fireTeam12.add(new NormalSoldier("伍長"));
fireTeam12.add(new LazySoldier("第二伍通訊兵"));
fireTeam12.add(new DrunkSoldier("Rick"));
fireTeam12.add(new DeserterSoldier("Jay"));
squad1.add(fireTeam12);
const squad2 = new ConcreteMilitaryUnit("第二班");
squad2.add(new NormalSoldier("班長"));
squad2.add(new NormalSoldier("第二班通訊兵"));
platoon.add(squad2);
const fireTeam21 = new ConcreteMilitaryUnit("第三伍");
fireTeam21.add(new NormalSoldier("伍長"));
fireTeam21.add(new DrunkSoldier("第三伍通訊兵"));
fireTeam21.add(new NormalSoldier("Allen"));
fireTeam21.add(new NormalSoldier("Bill"));
squad2.add(fireTeam21);
const fireTeam22 = new ConcreteMilitaryUnit("第四伍");
fireTeam22.add(new NormalSoldier("伍長"));
fireTeam22.add(new LazySoldier("第四伍通訊兵"));
fireTeam22.add(new DrunkSoldier("Charlie"));
fireTeam22.add(new DeserterSoldier("Dave"));
squad2.add(fireTeam22);
console.log("結構圖:");
army.display(1);
console.log("陸軍人員回報:" + army.reportUnitCounts());
console.log("陸軍武器數量回報:" + army.reportWeaponCounts());
}
armyCompositeSample();
Composite 是個好理解但是不容易實作的模式,原因有兩點:
符合需求的話,建構起來不麻煩,使用者只要參考規格就可以安心呼叫。
參考以上幾點,理解 Composite 是特殊要求下的解。
明天將介紹 Structural patterns 的第四個模式:Decorator 模式。